package cs.rsa.ts14.foxtrot;

import cs.rsa.ts14.framework.LineType;
import cs.rsa.ts14.framework.LineTypeClassifierStrategy;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static cs.rsa.ts14.framework.LineType.*;

public class LineTypeClassifierStrategyImpl implements LineTypeClassifierStrategy {

    static final String noError = "No error";
    List<Pair<Pattern, Validator>> patterns = new ArrayList<>();
    private String lastError;

    public LineTypeClassifierStrategyImpl() {
        lastError = noError;

        addPattern("^\\s*$", EMPTY_LINE);
        addPattern("^\\s*[A-Za-z]+\\s+=\\s+[-+]?\\d*\\.?\\d*(\\s*)$", new ValidateAssignmentRules());
        addPattern("^\\s*#", COMMENT_LINE);
        addPattern("^\\s*Week\\s+\\d{1,2}\\s+:\\s+[0-5]\\s+:\\s+[0-5]+(\\s*)$", new ValidateWeekRules());
        addPattern("^\\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\\s+(Bi|Ca|Pu|Tr|No|Ho)", WEEKDAY_SPECIFICATION);
        addPattern("^\\s+[a-zA-Z]\\w*\\s+([a-zA-Z]\\w*|\\-)\\s+\\d+(\\.0|\\.5)?(\\s+|$)", WORK_SPECIFICATION);

    }

    private void addPattern(String s, Validator validator) {
        Pair<Pattern, Validator> pair = Pair.of(Pattern.compile(s), validator);
        patterns.add(pair);
    }

    private void addPattern(String s, LineType type) {
        addPattern(s, new NullValidator(type));
    }

    @Override
    public LineType classify(String line) {
        if (line == null) {
            return invalid("Cannot parse a null line");
        }

        LineType type = INVALID_LINE;
        lastError = line + " : Unable to match line to a pattern";

        for (Pair<Pattern, Validator> entry : patterns) {
            Pattern pattern = entry.getLeft();
            Validator validator = entry.getRight();

            Matcher m = pattern.matcher(line);

            if (m.find()) {
                lastError = noError;
                type = validator.Validate(line);
                break;
            }
        }

        return type;
    }

    private LineType invalid(String msg) {
        lastError = msg;
        return INVALID_LINE;
    }

    @Override
    public String lastError() {
        return lastError;
    }

    private interface Validator {
        LineType Validate(String line);
    }

    private class ValidateWeekRules implements Validator {

        @Override
        public LineType Validate(String line) {
            String[] tokens = line.trim().split("\\s+");
            int workdays = Integer.parseInt(tokens[3]);
            int holidays = Integer.parseInt(tokens[5]);
            int weeknum = Integer.parseInt(tokens[1]);

            if ((workdays + holidays) > 5)
                return invalid(line + " : Sum of workdays and holidays is greater than 5");
            if (weeknum < 1)
                return invalid(line + " : week number is invalid: " + weeknum);
            if (weeknum > 53)
                return invalid(line + " : week number is invalid: " + weeknum);

            return WEEK_SPECIFICATION;
        }
    }

    private class NullValidator implements Validator {
        private LineType type;

        public NullValidator(LineType type) {
            this.type = type;
        }

        @Override
        public LineType Validate(String line) {
            return type;
        }

    }

    private class ValidateAssignmentRules implements Validator {
        @Override
        public LineType Validate(String line) {
            String[] tokens = line.trim().split("\\s+");
            try {
                double value = Double.parseDouble(tokens[2]);
                return ASSIGNMENT_LINE;
            } catch (Exception e) {
                return invalid(line + " failed to parse value");
            }
        }
    }
}
